package org.spbsu.apco.common.properties;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.*;

/**
 * User: solozobov
 * Date: 07.01.13
 */

public class ApplicationPropertiesManager {
    private ApplicationPropertiesManager(){}

    public static final Logger LOG = LoggerFactory.getLogger(ApplicationPropertiesManager.class);
    private static final String DEFAULT_PROPERTIES_FILE_NAME = "default.properties";
    private static final String LOCAL_PROPERTIES_FILE_NAME = "local.properties";

    private static final Map<PropertyKey, String> properties;

    @NotNull
    public static String getProperty(final PropertyKey propertyKey, @NotNull final String defaultValue) {
        final String propertyValue = getProperty(propertyKey);
        return propertyValue == null ? defaultValue : propertyValue;
    }

    @Nullable
    public static String getProperty(final PropertyKey propertyKey) {
        return properties.get(propertyKey);
    }


    static {
        final InputStream defaultPropertiesStream = getDefaultPropertiesFile();
        final HashMap<PropertyKey, String> defaultProperties = readProperties(defaultPropertiesStream);
        final InputStream localPropertiesStream = getLocalPropertiesFile();
        final HashMap<PropertyKey, String> localProperties = readProperties(localPropertiesStream);
        defaultProperties.putAll(localProperties);
        validateProperties(defaultProperties);
        properties = defaultProperties;
    }

    private static InputStream getDefaultPropertiesFile() {
        return ApplicationPropertiesManager.class.getResourceAsStream("/" + DEFAULT_PROPERTIES_FILE_NAME);
    }

    private static InputStream getLocalPropertiesFile() {
        try {
            return new FileInputStream(LOCAL_PROPERTIES_FILE_NAME);
        } catch (FileNotFoundException e) {
            LOG.info("Local properties file \"" + new File(LOCAL_PROPERTIES_FILE_NAME).getAbsolutePath() + "\" not found or empty");
            return null;
        }
    }

    private static HashMap<PropertyKey, String> readProperties(final InputStream propertiesStream) {
        if (propertiesStream == null) {
            return new HashMap<PropertyKey, String>();
        }

        final Map<String, PropertyKey> propertyNameToKey = makePropertyNameToPropertyMap();
        final Scanner propertiesScanner = new Scanner(propertiesStream);

        try {
            final HashMap<PropertyKey, String> result = new HashMap<PropertyKey, String>();

            while (propertiesScanner.hasNextLine()) {
                final String line = propertiesScanner.nextLine();
                final String[] parts = line.split("=", 2);
                if (parts.length != 2) {
                    throw new RuntimeException("Properties file line is invalid " + line);
                }
             
                final String propertyKeyName = parts[0];
                final PropertyKey existingProperyKey = propertyNameToKey.get(propertyKeyName);
                if (result.containsKey(existingProperyKey)) {
                    throw new RuntimeException("Property key name " + propertyKeyName + " duplicates");
                }
    
                result.put(existingProperyKey, parts[1]);  
            }
            return result;
        } finally {
            propertiesScanner.close();
        }
    }

    private static Map<String, PropertyKey> makePropertyNameToPropertyMap() {
        final Map<String, PropertyKey> result = new HashMap<String, PropertyKey>();
        for (final PropertyKey propertyKey : PropertyKey.values()) {
            final String propertyKeyName = propertyKey.propertyName;
        
            final PropertyKey existingProperyKey = result.get(propertyKeyName);
            if (existingProperyKey != null) {
                throw new RuntimeException("Property key name " + propertyKeyName + " duplicates for " + existingProperyKey.name() + " and " + propertyKey.name());
            }
        
            result.put(propertyKeyName, propertyKey);
        }
        return result;
    }
    
    private static void validateProperties(final Map<PropertyKey, String> properties) {
        final Set<PropertyKeyGroup> obligatoryProperyKeyGroups = new HashSet<PropertyKeyGroup>();
        final String targets = properties.get(PropertyKey.TARGET);
        if (targets == null) {
            LOG.warn("Can't find \"target\" property in " + new File(DEFAULT_PROPERTIES_FILE_NAME).getAbsolutePath());
        }
        else {
            for (final String propertyKeyGroupName : targets.split(",")) {
                obligatoryProperyKeyGroups.add(PropertyKeyGroup.valueOf(propertyKeyGroupName.toUpperCase()));
            }
            final Set<PropertyKey> missedPropertyKeys = new HashSet<PropertyKey>();
            for (final PropertyKey propertyKey : PropertyKey.values()) {
                for (final PropertyKeyGroup propertyKeyGroup : propertyKey.propertyKeyGroups) {
                    if (obligatoryProperyKeyGroups.contains(propertyKeyGroup)) {
                        missedPropertyKeys.add(propertyKey);
                    }
                }
            }
            missedPropertyKeys.removeAll(properties.keySet());
            if (missedPropertyKeys.size() > 0) {
                final StringBuilder builder = new StringBuilder("Properties missing: ");
                for (final PropertyKey missedPropertyKey : missedPropertyKeys) {
                    builder.append(missedPropertyKey.propertyName).append(", ");
                }
                LOG.warn(builder.toString());
            }
        }
    }
}
